package middleware

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"

	// "github.com/golang-jwt/jwt"
	"github.com/golang-jwt/jwt/v5" // v5 新版
	"github.com/google/uuid"
)

var jwtSecret = []byte("32-byte-long-secret-key-for-hs256") // 默认密钥，生产环境应该从环境变量读取

// 初始化JWT密钥
func init() {
	//os.Getenv 就是环境变量 就是在zsh中配置的 比如mac就是 .zshrc
	if secret := os.Getenv("JWT_SECRET"); secret != "" {
		jwtSecret = []byte(secret)
	}
}

// JWT声明结构体
type Claims struct {
	UserID string   `json:"userID"`
	Roles  []string `json:"roles"`
	jwt.RegisteredClaims
}

/* 生成JWT令牌   JWT 是 JSON Web Token 的缩写，直译就是 “基于 JSON 的网络令牌”。

部分		内容说明								例子
Header		令牌类型 + 签名算法				`{"alg":"HS256","typ":"JWT"}`
Payload		声明（标准 + 自定义）			`{"sub":"123","name":"aa","exp":1696545600}`
Signature	防篡改签名					`HMACSHA256(base64Url(header) + '.' + base64Url(payload), 密钥)`

为什么用它
1.
无状态：服务器签完即可，不用保存会话。
2.
跨域/跨服务：在微服务、移动端、单页应用里随意传。
3.
自包含：载荷里直接带用户名、角色、过期时间等，一次验证即可。

常见流程
1.
用户登录 → 服务器验证账号密码。
2.
服务器生成 JWT 并签名 → 返回给前端。
3.
前端后续请求在 Header 里带
  Authorization: Bearer <JWT>
4.
服务器用密钥验签 → 通过就认为是合法用户，不再查库。




Claims 直译 “声明”，在 JWT 里就是 “这张令牌里携带的数据” 的统称。 宣称；声称；获得；要求(拥有)
它其实是一份 JSON 对象，里面一个个键值对都叫一条 claim（声明），用来描述 “谁、什么时候、什么权限、有效期多久” 等信息。
两种声明
1.
标准声明（RFC 7519 规定，可选）
 | 字段 | 含义 |  |------|------|  |  iss  (Issuer) | 签发者 |  |
 sub  (Subject) | 主题（通常是用户 ID） |  |  aud  (Audience) | 受众
 |  |  exp  (ExpiresAt) | 过期时间（Unix 秒） |  |  nbf  (NotBefore)
 | 在此之前不可用 |  |  iat  (IssuedAt) | 签发时间 |  |  jti  (JWT ID) | 令牌唯一编号 |


 2.
自定义声明（自己随便加）
 例如 Go 代码里写的：
go
type Claims struct {
    UserID string   `json:"userID"` // 自定义
    Roles  []string `json:"roles"`  // 自定义
    jwt.StandardClaims           // 标准声明全部嵌入
}

最终生成的 Payload 长这样：
json
{
  "userID": "42",
  "roles": ["admin"],
  "exp": 1696627200,
  "iat": 1696540800,
  "iss": "myapp"
}

Payload（中文常叫“有效载荷”或“净荷”）就是真正想要传输、保存或处理的那些数据，
区别于为了完成传输而额外加上的“包装/控制/元数据”。
Header 负责“怎么送”，Payload 负责“送什么”。

场景 Header / 包装                         Payload
HTTP 状态行 + 请求/响应头               请求体/响应体（JSON、HTML、文件…）
TCP/IP 各层首部（IP 头、TCP 头）        真正的应用数据 JWT
Header + Signature                   Payload（存放用户 ID、角色、过期时间等声明）
消息队列 消息属性（路由键、时间戳）        业务 JSON / Protobuf
文件传输 文件名、校验和、分片序号 			文件二进制内容


POST /login HTTP/1.1              ← 起始行 + 头部
Content-Type: application/json
Content-Length: 43

{"username":"tom","password":"123"} ← 这就是 Payload
*/

/*
type StandardClaims struct {
	Audience  string `json:"aud,omitempty"`  // 受众
	ExpiresAt int64  `json:"exp,omitempty"`  // 过期时间（Unix 秒）
	Id        string `json:"jti,omitempty"`  // JWT ID
	IssuedAt  int64  `json:"iat,omitempty"`  // 签发时间
	Issuer    string `json:"iss,omitempty"`  // 签发者
	NotBefore int64  `json:"nbf,omitempty"`  // 在此时间之前无效
	Subject   string `json:"sub,omitempty"`  // 主题
}
*/

func GenerateToken(userID string, roles []string) (string, error) {
	now := time.Now()

	claims := Claims{
		UserID: userID,
		Roles:  roles,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(now.Add(8 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(now),
			Issuer:    "middleware-demo",
		},
	}
	//header  签名类型是HS256  签名算法	jwt
	//payload 是claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

/*
这段代码本身已经能跑，但有几个生产环境必改的小坑：
1.
 jwt.StandardClaims  已弃用
 换  jwt.RegisteredClaims （支持 slice audience、time.Time 等）。
2.
时间用  time.Time  而不是  Unix()  秒，省去手动转。
3.
 jwtSecret  必须是  []byte  类型，且长度 ≥ 32 字节（HS256 建议）。
4.
把常量提出来，方便测试与轮换
*/

// 1. 自定义载荷
type Claims1 struct {
	UserID               string   `json:"user_id"`
	Roles                []string `json:"roles"`
	jwt.RegisteredClaims          // 内嵌官方标准字段
}

// JWT鉴权中间件

// header里面 添加 Authorization  值为 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIiwidXNlciJdLCJleHAiOjE3NTcwODYwNDksImlhdCI6MTc1NzA1NzI0OSwiaXNzIjoibWlkZGxld2FyZS1kZW1vIn0.QlSY4G-gHhKpWRxIix0cIwZyJkuhuAWkrlBiE8ietNY
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 添加请求ID用于调试追踪
		requestID := uuid.NewString()
		c.Set("requestID", requestID)

		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error":     "missing authorization header",
				"requestID": requestID,
			})
			return
		}

		// 		如果  authHeader  以  Bearer   开头，返回砍掉前缀后的纯 token。
		// 如果 没有 该前缀，返回 原字符串不变

		// 检查Bearer前缀  Bearer是固定格式 不能更改
		//“确认客户端真的写了  Bearer   前缀，而不是直接把裸 token 塞进 Authorization 头
		tokenString := strings.TrimPrefix(authHeader, "Bearer ") //authHeader砍掉前缀Bearer剩余字符串

		//  if tokenString == authHeader
		//  比较结果：
		// 二者相等 → 说明 根本没被砍掉，前缀缺失 → 格式非法。
		// 二者不等 → 说明前缀存在，且已被砍掉，继续走后续验签。

		if tokenString == authHeader {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error":       "invalid authorization format",
				"requestID":   requestID,
				"authHeader":  authHeader,
				"tokenString": tokenString,
			})
			return
		}

		/* 解析JWT token 这行代码只做一件事：确认 JWT 用的签名算法必须是 HMAC 系列（HS256 / HS384 / HS512），
				 而不是 RSA、ECDSA 等其他算法，防止“算法混淆攻击”（algorithm substitution attack）。
		.
				token.Method
				接口类型  jwt.SigningMethod ，实际可以存放 HS256、RS256、ES256 等各种算法的实现
				.(*jwt.SigningMethodHMAC)
				这是 类型断言（type assertion）：
				“我断言这个接口变量真正的动态类型是  *jwt.SigningMethodHMAC 。”
				如果猜对了，返回该类型实例，布尔值  ok == true 。
				如果实际存放的是  *SigningMethodRSA  等，断言失败， ok == false

				3.
				if _, ok := ...; !ok { return nil, error }
				一旦断言失败，说明签名算法不是 HMAC，立即报错，拒绝解析，防止有人把公钥算法令牌当成 HMAC 令牌骗过验证。
				一句话
				“这行就是在门卫处检查：进来的令牌必须是我们事先约好的 HMAC 算法签的，别的算法一律不收。”

		*/

		token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return jwtSecret, nil
		})

		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error":     "invalid token: " + err.Error(),
				"requestID": requestID,
			})
			return
		}

		/*
		 token.Claims  是接口，必须断言成我们自定义的  *Claims  结构
		 只有断言成功且  token.Valid  为真（签名、格式、标准字段都合法）才继续。
		*/
		if claims, ok := token.Claims.(*Claims); ok && token.Valid {
			// 检查token是否过期
			if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) { //2. 二次校验过期时间
				c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
					"error":     "token expired",
					"requestID": requestID,
				})
				return
			}
			/*
				把用户信息写进 Gin 上下文
				c.Set("userID", claims.UserID)
				c.Set("roles",  claims.Roles)

				后续任何控制器/中间件都能通过
				uid := c.MustGet("userID").(string)
				roles := c.MustGet("roles").([]string)
				拿到当前登录用户数据，无需再解析一次 JWT。
			*/

			c.Set("userID", claims.UserID)
			c.Set("roles", claims.Roles)

			//4. 记录审计日志
			log.Printf("[%s] JWT Auth Success - User: %s, Roles: %v", requestID, claims.UserID, claims.Roles)
			// 放行请求
			c.Next()
		} else {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error":     "invalid token claims",
				"requestID": requestID,
			})
		}
	}
}

// RBAC权限中间件 - 支持多角色验证
func RequireRole(requiredRoles ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		requestID := c.GetString("requestID")

		rolesInterface, exists := c.Get("roles")
		if !exists {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"error":     "no roles found in context",
				"requestID": requestID,
			})
			return
		}

		//：把“空接口”里取出来的值，还原成真正的  []string  类型，否则后面没法当字符串数组用。
		userRoles, ok := rolesInterface.([]string)
		if !ok {
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
				"error":     "invalid roles format",
				"requestID": requestID,
			})
			return
		}

		// 检查用户是否拥有任一所需角色
		for _, requiredRole := range requiredRoles {
			for _, userRole := range userRoles {
				if userRole == requiredRole {
					log.Printf("[%s] RBAC Auth Success - Required: %v, User has: %s", requestID, requiredRoles, userRole)
					c.Next()
					return
				}
			}
		}

		log.Printf("[%s] RBAC Auth Failed - Required: %v, User has: %v", requestID, requiredRoles, userRoles)
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
			"error":     fmt.Sprintf("insufficient permissions, required roles: %v", requiredRoles),
			"requestID": requestID,
		})
	}
}

// 跨域中间件配置
func CORSMiddleware() gin.HandlerFunc {
	return cors.New(cors.Config{
		//白名单域名
		AllowOrigins: []string{"https://prod.com", "http://localhost:3000", "http://localhost:8080"},
		AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
		//前端请求头里允许出现的字段；不在列表里的头会被拒。
		AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Requested-With"},
		//让浏览器能读取的响应头（默认只能拿 6 个基本头）
		ExposeHeaders: []string{"Content-Length", "X-Request-ID"},
		//允许带 cookie / Authorization cookie；设为 true 时  AllowOrigins  不能写  *  必须显式域名
		AllowCredentials: true,
		//预检  OPTIONS  结果缓存 12 小时，减少重复请求。
		MaxAge: 12 * time.Hour,
		//之后前端（如  https://prod.com  或本地  localhost:3000 ）就能跨域访问你的接口，
		// 同时支持 JWT 鉴权、自定义头、Cookie 等场景
	})
}

// 请求日志中间件 - 支持耗时统计和敏感信息过滤
func RequestLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()

		// 获取或生成请求ID
		requestID := c.GetString("requestID")
		if requestID == "" {
			requestID = uuid.NewString()
			c.Set("requestID", requestID)
		}

		// 记录请求体（用于调试，生产环境可能需要关闭）
		var requestBody []byte
		if c.Request.Body != nil {
			requestBody, _ = io.ReadAll(c.Request.Body)
			c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
		}

		// 过滤敏感路径的日志
		shouldLogBody := !isSensitivePath(c.Request.URL.Path)

		log.Printf("[%s] Request Start: %s %s from %s",
			requestID, c.Request.Method, c.Request.URL.Path, c.ClientIP())

		if shouldLogBody && len(requestBody) > 0 && len(requestBody) < 1024 {
			log.Printf("[%s] Request Body: %s", requestID, string(requestBody))
		}

		// 设置响应头
		c.Header("X-Request-ID", requestID)

		c.Next()

		// 记录响应信息
		latency := time.Since(start)
		status := c.Writer.Status()

		log.Printf("[%s] Request End: %s %s - Status: %d, Latency: %v, Size: %d",
			requestID, c.Request.Method, c.Request.URL.Path, status, latency, c.Writer.Size())

		// 记录错误信息
		if len(c.Errors) > 0 {
			log.Printf("[%s] Errors: %v", requestID, c.Errors.String())
		}
	}
}

// Recovery中间件 - 处理panic恢复
func Recovery() gin.HandlerFunc {
	return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
		requestID := c.GetString("requestID")
		if requestID == "" {
			requestID = uuid.NewString()
		}

		log.Printf("[%s] PANIC RECOVERED: %v", requestID, recovered)

		c.JSON(http.StatusInternalServerError, gin.H{
			"error":     "internal server error",
			"requestID": requestID,
		})
	})
}

// 检查是否为敏感路径
func isSensitivePath(path string) bool {
	sensitivePaths := []string{"/auth", "/login", "/register", "/password"}
	for _, sensitive := range sensitivePaths {
		if strings.Contains(path, sensitive) {
			return true
		}
	}
	return false
}

// 耗时统计中间件（简化版，主要功能已集成到RequestLogger中）
func LatencyLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		latency := time.Since(start)

		requestID := c.GetString("requestID")
		log.Printf("[%s] Latency: %s %s cost: %v",
			requestID, c.Request.Method, c.Request.URL.Path, latency)
	}
}
